﻿#region Using Statements
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using DirectZZT.Core;
using DirectZZT.Gameplay;
using DirectZZT.Shared.Core;
using DirectZZT.Shared.Gameplay;
using DirectZZT.Shared.Input;
using DirectZZT.Shared.Screens;
using DirectZZT.Shared.Scripting;
using DirectZZT.Shared.World;
using DirectZZT.Shared.World.Objects;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
#endregion

namespace DirectZZT.Screens
{
    /// <summary>
    /// Provides the screen on which the world is played.
    /// </summary>
    class GameScreen : ScreenBase
    {
        #region Fields

        private World _currentWorld;
        private SpriteBatch _spriteBatchHUD;
        private SpriteFont _defaultFont;
        private IInputManager _input;

        private IngameMenuScreen _ingameMenu;

        private Matrix _matrix;
        private bool _useModernView;
        private GameMode _mode;

        private Dictionary<int, Player> _players;
        private Player _localPlayer;

        #endregion

        #region Constructors

        /// <summary>
        /// 
        /// </summary>
        public GameScreen()
        {
            _mode = GameMode.Game;
            _matrix = Matrix.Identity * Matrix.CreateScale(0.4f);
            _players = new Dictionary<int, Player>();
            _localPlayer = new Player();

            this.ForceProcessingWhileInactive = true;

            // load settings
            _useModernView = !ConfigurationManager.Get("Client").GetBoolean("G.ClassicView", true);
        }

        #endregion

        #region Methods

        private void DisposeCurrentWorld()
        {
            if (_currentWorld != null)
            {
                Logging.GetLogging().Log(LoggingMessageType.Info, "Game", "", "Disposing current world in progres...");

                // stop and save board
                _currentWorld.Stop();

                //// save world
                //IWorldSerializer serializer = TypeManager.Instance.Import<IWorldSerializer>("DirectZZTWorldSerializer");
                //using (FileStream stream = new FileStream("D:\\MyWorld." + serializer.Extension, FileMode.Create))
                //{
                //    serializer.Save(_currentWorld, stream, new WorldSaveOptions() { TargetFileName = stream.Name });
                //}

                _currentWorld.Dispose();
                _currentWorld = null;

                Logging.GetLogging().Log(LoggingMessageType.Info, "Game", "", "Current world disposed.");
            }
        }

        private void LoadWorldFile(string path, string worldSerializer)
        {
            // dispose current world
            DisposeCurrentWorld();

            // switch to game mode
            _mode = GameMode.Game;

            // load world
            Logging.GetLogging().LogFormat(LoggingMessageType.Trace, "Game", "", "Begin loading of world file '{0}'...", path);
            Stopwatch sw = Stopwatch.StartNew();

            _currentWorld = new World(this.ScreenManager.Game);
            _currentWorld.BeginInit();

            // load the world using the requested serializer
            IWorldSerializer serializer = TypeManager.Instance.Import<IWorldSerializer>(worldSerializer);
            using (FileStream stream = new FileStream(path, FileMode.Open))
            {
                serializer.Load(stream, _currentWorld, new WorldDeserializeOptions() { SourceFileName = stream.Name, StrictMode = true });
            }
            _currentWorld.EndInit();
            _currentWorld.SetActiveBoard(_currentWorld.Boards[_currentWorld.InitialBoard]);

            // get active player
            IPlayerBoardObject pbo = _currentWorld.CurrentBoard.GetPlayers().FirstOrDefault() as IPlayerBoardObject;
            if (pbo != null)
            {
                _localPlayer = pbo.Player;
            }

            sw.Stop();
            Logging.GetLogging().LogFormat(LoggingMessageType.Trace, "Game", "", "Loaded world '{0}' ({1} board(s)) in {2} milliseconds.", _currentWorld.Title, _currentWorld.Boards.Count - 1, sw.ElapsedMilliseconds);

            this.ScreenManager.Game.Window.Title = "DirectZZT - " + path;

        }

        private void CreateCloseUpMatrix()
        {
            // get screen's width and height
            float scrw = this.ScreenManager.Game.GraphicsDevice.Viewport.Width;
            float scrh = this.ScreenManager.Game.GraphicsDevice.Viewport.Height;
            //float scrw = Board.BoardWidth;
            //float scrh = Board.BoardHeight;

            // get real position
            int locX = _localPlayer.BoardObject.X * 32;
            int locY = _localPlayer.BoardObject.Y * 32;

            float perX = (float)((locX * 100) / scrw);
            float perY = (float)((locY * 100) / scrh);

            // TODO: fine-tuning
            _matrix = Matrix.Identity * Matrix.CreateTranslation(-(perX * 5.75f), -(perY * 2.75f), 0.0f);


            //_matrix = Matrix.Identity * Matrix.CreateTranslation(GetCUMValue(perX), GetCUMValue(perY), 0f);

            // apply to rendering
            _currentWorld.RenderMatrix = _matrix;
        }

        private float GetCUMValue(float x)
        {
            float a = 0.125f;
            //float x = 1.0f;
            float b = 3.0f;
            float c = 2.0f;

            // y = ax2 + bx + c
            return -((a * (x * x)) + (b * x) + c);
        }

        #endregion

        #region ScreenBase Members

        /// <summary>
        /// Called on attach
        /// </summary>
        /// <param name="screenManager"></param>
        public override void OnAttach(IScreenManager screenManager)
        {
            base.OnAttach(screenManager);

            // Create a new SpriteBatch, which can be used to draw textures.
            _spriteBatchHUD = new SpriteBatch(this.ScreenManager.Game.GraphicsDevice);
            _defaultFont = this.ScreenManager.Game.Content.Load<SpriteFont>("Fonts/HUDFont");
            _input = this.ScreenManager.Game.Services.GetService(typeof(IInputManager)) as IInputManager;

            // create ingame menu screen
            _ingameMenu = new IngameMenuScreen();

            // cheer up the player on startup a bit
            SetupTestBoard();
        }

        /// <summary>
        /// Updates the screen.
        /// </summary>
        /// <param name="gameTime"></param>
        public override void Update(GameTime gameTime)
        {
#if WINDOWS
            if (_input.GetActionInvokedToggle("Windows.ManageWorlds"))
            {
                if (!DirectZZT.Forms.ManageWorldWindow.IsOpen)
                {
                    DirectZZT.Forms.ManageWorldWindow window = new Forms.ManageWorldWindow();
                    if (window.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                    {
                        switch (window.SelectedAction)
                        {
                            case Forms.ManageWorldWindow.Action.Open:
                                {
                                    LoadWorldFile(window.WorldPath, window.WorldSerializer);
                                } break;
                            case Forms.ManageWorldWindow.Action.Save:
                                {

                                } break;
                            case Forms.ManageWorldWindow.Action.CreateNew:
                                {
                                    DisposeCurrentWorld();

                                    // automatically turn into Editor mode
                                    _mode = GameMode.Editor;

                                    _currentWorld = new World(this.ScreenManager.Game);
                                    _currentWorld.FeatureSet = FeatureSet.DirectZZT;
                                    _currentWorld.Author = Environment.UserName;
                                    _currentWorld.Title = "New world";

                                    // add new, blank boards (one for title, one regular)
                                    _currentWorld.BeginInit();
                                    _currentWorld.AddBoard(new Board()
                                    {
                                        Name = "Title board",
                                        Title = "Title board",
                                    });
                                    _currentWorld.AddBoard(new Board()
                                    {
                                        Name = "Blank board #1",
                                        Title = "Blank board #1",
                                    });
                                    _currentWorld.InitialBoard = 1;
                                    _currentWorld.SetActiveBoard(_currentWorld.InitialBoard);
                                    _currentWorld.EndInit();

                                } break;
                            default: break;
                        }
                    }
                }
            }

            if (_input.GetActionInvoked("Modifiers.Alt"))
            {
                if (_input.GetActionInvokedToggle("Windows.ScriptDebugger"))
                {
                    if (!DirectZZT.Forms.ScriptDebuggerWindow.IsOpen)
                    {
                        DirectZZT.Forms.ScriptDebuggerWindow.IsOpen = true;
                        new DirectZZT.Forms.ScriptDebuggerWindow().Show();
                    }
                }
                if (_input.GetActionInvokedToggle("Windows.Log"))
                {
                    if (!DirectZZT.Forms.LoggerWindow.IsOpen)
                    {
                        new DirectZZT.Forms.LoggerWindow().Show();
                    }
                }
            }
#endif

            if (_input.GetActionInvokedToggle("Menu.Back"))
            {
                // invoke ingame menu screen
                if (!_ingameMenu.IsActive)
                {

                    this.ScreenManager.PushScreen(_ingameMenu, false);
                }
            }


            if (_currentWorld != null)
            {
                _currentWorld.Update(gameTime);

                if (_useModernView && _localPlayer.BoardObject != null)
                {
                    CreateCloseUpMatrix();
                }
                else
                {
                    _currentWorld.RenderMatrix = _matrix;
                }
            }
        }

        /// <summary>
        /// Draws the screen.
        /// </summary>
        /// <param name="gameTime"></param>
        public override void Draw(GameTime gameTime)
        {
            // TODO: Add your drawing code here
            if (_currentWorld != null)
            {
                _currentWorld.Draw(gameTime);
            }

            // draw the HUD last
            HUDDraw();
        }

        /// <summary>
        /// Disposes this screen.
        /// </summary>
        public override void Dispose()
        {
            base.Dispose();


            // exit game
            if (_currentWorld != null)
            {
                _currentWorld.Stop();
                _currentWorld.Dispose();
            }
        }

        #endregion

        #region HUD

        private void HUDDraw()
        {
            _spriteBatchHUD.Begin();

            if (_currentWorld != null)
            {
                string text = string.Format("{0} Bullet(s)\n{1} Torch(es)\nHP: {2}\nArmor: {3}",
                    _localPlayer.Inventory.AmmoAmount,
                    _localPlayer.Inventory.TorchAmount,
                    _localPlayer.Health,
                    _localPlayer.Armor);

                if (_mode == GameMode.Editor)
                {
                    _spriteBatchHUD.DrawString(_defaultFont, "EDITOR MODE", new Vector2(GetX(80.0f), GetY(5.0f)), Color.Red);
                }

                _spriteBatchHUD.DrawString(_defaultFont, text, new Vector2(GetX(2.0f), GetY(80.0f)), Color.Green);
            }
            else
            {
                _spriteBatchHUD.DrawString(_defaultFont, "No world loaded. Please load a world (Menu or F10).", new Vector2(16, 64), Color.Red);
            }


            _spriteBatchHUD.End();
        }

        #endregion

        #region Editor methods

        /// <summary>
        /// Outputs the grid location of the cursor.
        /// </summary>
        /// <param name="mouseX">The x-value of the cursor.</param>
        /// <param name="mouseY">The y-value of the cursor</param>
        /// <param name="x">The integer result on the x-axis.</param>
        /// <param name="y">The integer result on the y-axis.</param>
        private void TryGetGridLocation(float mouseX, float mouseY, out int x, out int y)
        {
            var result = this.ScreenManager.Game.GraphicsDevice.Viewport.Unproject(new Vector3(mouseX, mouseY, 0f), _matrix, _matrix, _matrix);
            x = (int)(result.X / 32);
            y = (int)(result.Y / 32);
        }

        #endregion

        #region --- TEST BOARD ---

        private void SetupTestBoard()
        {
            _currentWorld = new World(this.ScreenManager.Game);

            Board _testBoard = new Board();
            _testBoard.Name = "TestBoard";
            _currentWorld.BeginInit();
            _currentWorld.AddBoard(_testBoard);

            _testBoard.SpawnObject(new DirectZZT.BoardObjects.PlayerBoardObject() { X = 10, Y = 10 }, null);

            int i = 0;
            for (int x = 0; x < 20; x++)
            {
                for (int y = 0; y < 10; y++)
                {
                    if ((x == 0 || x == 19) || (y == 0 || y == 9))
                    {
                        IScript script = Script.Parse(new string[] { 
                            string.Format("@Object{0}", i),

                            string.Format(":boring_loop"),
                            string.Format("#SET myflag"),
                            string.Format("#CLEAR myflag"),
                            string.Format("#CLEAR non_existant_flag"),

                            string.Format("#IF non_existant_flag THEN boring_loop"),

                            string.Format("#SEND Object0:boring_loop"),
                            string.Format("#SEND boring_loop"),
                            }
                            );

                        BoardObject bo = new DirectZZT.BoardObjects.WallBoardObject() { X = x, Y = y };
                        _testBoard.SpawnObject(bo, null);

                        bo.AttachScript(script);

                        i++;
                    }
                }
            }
            _currentWorld.EndInit();
            _currentWorld.SetActiveBoard(_currentWorld.Boards[_currentWorld.InitialBoard]);

            // get active player (in this test-setup, it's only one)
            IPlayerBoardObject pbo = _currentWorld.CurrentBoard.GetPlayers()[0] as IPlayerBoardObject;
            _localPlayer = pbo.Player;

            this.ScreenManager.Game.Window.Title = "DirectZZT - Demo (press F10 to load world)";
        }

        #endregion
    }
}
